<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>字体解析示例</title>
</head>
<body>
<input type="file" onchange="fileChange(this.files[0])" accept=".ttf"/>
<script>
    // 解析头信息
    function parseFont(arrayBuffer) {
        let offset = 0;
        const dataView = new DataView(arrayBuffer);
        const version = dataView.getUint32(offset);
        offset += 4;
        console.log("字体版本: 0x" + version.toString(16));
        const numTables = dataView.getUint16(offset);
        offset += 2;
        console.log("字体表数量: " + numTables);
        // searchRange到rangeShift变量用于快速查找字体表，暂未用到。好像也不建议用这个参数了。直接跳过
        offset += 6;
        const tableDirectory = {};// 字体表的定义集合: tag-name={checksum , offset , length}
        for (let i = 0; i < numTables; i++) {
            // const tag = dataView.getUint32(offset);
            // 将标签值转换为字符串，更容易理解
            const tag = String.fromCharCode(
                dataView.getUint8(offset),
                dataView.getUint8(offset + 1),
                dataView.getUint8(offset + 2),
                dataView.getUint8(offset + 3)
            );
            offset += 4;
            const checksum = dataView.getUint32(offset);
            offset += 4;
            const tableOffset = dataView.getUint32(offset);
            offset += 4;
            const length = dataView.getUint32(offset);
            offset += 4;
            tableDirectory[tag] = {checksum, offset: tableOffset, length};
        }
        console.log(tableDirectory);
        return tableDirectory;
    }

    function parseName(arrayBuffer, header) {
        const dataView = new DataView(arrayBuffer);
        let offset = header.offset;
        const version = dataView.getUint16(offset); // 这个版本标记这之后如何读取内容
        console.log("name表版本: " + version);
        offset += 2;
        const count = dataView.getUint16(offset);
        offset += 2;
        console.log("name表数量: " + count);
        // 定义中数据格式为Offset16 在文档开有说明：Offset16	表的短偏移量，与 uint16 相同，NULL 偏移量 = 0x0000
        const storageOffset = dataView.getUint16(offset);
        offset += 2;
        const nameRecords = [];
        if (version === 0) {
            // 版本0就按照版本0格式读取
            // 有count个NameRecord
            for (let i = 0; i < count; i++) {
                const platformID = dataView.getUint16(offset);
                offset += 2;
                const encodingID = dataView.getUint16(offset);
                offset += 2;
                const languageID = dataView.getUint16(offset);
                offset += 2;
                const nameID = dataView.getUint16(offset);
                offset += 2;
                const length = dataView.getUint16(offset);
                offset += 2;
                const stringOffset = dataView.getUint16(offset);
                offset += 2;
                nameRecords.push({
                    platformID,
                    encodingID,
                    languageID,
                    nameID,
                    length,
                    stringOffset
                });
            }
        }
        console.log(nameRecords)
        // 读取字符串，每个record都有一个字符串偏移位置
        for (let i = 0; i < nameRecords.length; i++) {
            const record = nameRecords[i];
            const stringBytes = new Uint8Array(dataView.buffer, header.offset + storageOffset + record.stringOffset, record.length);
            // 上面是读取到的字节数组，每个平台的字符串编码都不一样，需要根据platformID和encodingID来判断。
            // 编码表可以参考：https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f
            record.stringData = stringBytes;
            // 输出window平台展示的字符串
            // 文档原文：all string data for platform 3 must be encoded in UTF-16BE.
            // 就是windows平台所有的字符串都是UTF-16BE编码的，所以需要用TextDecoder来解码。
            if (record.platformID === 3 && record.encodingID === 1) {
                // UTF-16BE 是一种使用大端字节序的 UTF-16 编码方式。JavaScript 的字符串默认是以 UTF-16 编码存储的，但需要手动处理字节序问题。
                // 将 UTF-16BE 转换为 UTF-16LE
                function convertUTF16BEtoUTF16LE(utf16beBytes) {
                    const utf16leBytes = new Uint8Array(utf16beBytes.length);
                    for (let i = 0; i < utf16beBytes.length; i += 2) {
                        utf16leBytes[i] = utf16beBytes[i + 1];     // 高位字节
                        utf16leBytes[i + 1] = utf16beBytes[i];     // 低位字节
                    }
                    return utf16leBytes;
                }

                // 英文
                if (record.languageID === 0x0409) {
                    // 读取字符串
                    const string = new TextDecoder("utf-16le").decode(convertUTF16BEtoUTF16LE(stringBytes));
                    console.log("Windows平台英文:" + record.nameID + " " + string);
                }
                // 中文
                if (record.languageID === 0x0804) {
                    // 读取字符串
                    const string = new TextDecoder("utf-16le").decode(convertUTF16BEtoUTF16LE(stringBytes));
                    console.log("Windows平台中文:" + record.nameID + " " + string);
                }
            }
        }
    }

    // 读取文件
    function fileChange(file) {
        let reader = new FileReader();
        reader.readAsArrayBuffer(file);
        reader.onload = function () {
            const headers = parseFont(reader.result);
            parseName(reader.result, headers.name);
        }
    }
</script>
</body>
</html>
